package src

import (
    "errors"
    "fmt"
)

const (
    rows    = 9
    columns = 9
)

type SudokuVal struct {
    Val    int8   `json:"value"`
    Preset bool   `json:"isPreset"`
    Area   string `json:"area"`
}

type Coordinate struct {
    X int `json:"posX"` // row
    Y int `json:"posY"` // col
}

type SudokuMethod interface {
    Display()
    fill(val int8, pos Coordinate) error
    clear(pos Coordinate) error
    isFinish() bool
    ruleCheck(val int8, pos Coordinate) error
}

type Sudoku [rows][columns]SudokuVal

func NewSudoku(s [rows][columns]int8) Sudoku {
    var sudoku Sudoku
    for x := 0; x < len(s); x++ {
        for y := 0; y < len(s[x]); y++ {
            switch {
            case x < 3:
                switch {
                case y < 3:
                    sudoku[x][y].Area = "a"
                case y < 6:
                    sudoku[x][y].Area = "b"
                default:
                    sudoku[x][y].Area = "c"
                }
            case x < 6:
                switch {
                case y < 3:
                    sudoku[x][y].Area = "d"
                case y < 6:
                    sudoku[x][y].Area = "e"
                default:
                    sudoku[x][y].Area = "f"
                }
            default:
                switch {
                case y < 3:
                    sudoku[x][y].Area = "g"
                case y < 6:
                    sudoku[x][y].Area = "h"
                default:
                    sudoku[x][y].Area = "i"
                }
            }

            switch s[x][y] {
            case 0:
                sudoku[x][y].Preset = false
            default:
                sudoku[x][y].Preset = true
            }
            sudoku[x][y].Val = s[x][y]
        }
    }
    return sudoku
}

func inputCheck(params ...interface{}) bool {
    length := len(params)
    switch length {
    case 1:
        pos := params[0].(Coordinate)
        return pos.X >= 0 && pos.X < 9 && pos.Y >= 0 && pos.Y < 9

    case 2:
        val := params[0].(int8)
        pos := params[1].(Coordinate)
        return val > 0 && val <= 9 && pos.X >= 0 && pos.X < 9 && pos.Y >= 0 && pos.Y < 9
    default:
        return false
    }
}

func handleSudoku(s *Sudoku) {
    var handle string
    fmt.Print("请选择操作(c为清除, f为填入): ")
    fmt.Scanln(&handle)

    switch handle {
    case "f":
        var (
            val int8
            pos Coordinate
        )
        fmt.Print("请输入值与坐标: ")
        fmt.Scanf("%v %v,%v", &val, &pos.X, &pos.Y)

        err := s.fill(val, pos)
        if err != nil {
            fmt.Println(err)
        }
    case "c":
        var pos Coordinate
        fmt.Print("请输入坐标: ")
        fmt.Scanf("%v,%v", &pos.X, &pos.Y)

        err := s.clear(pos)
        if err != nil {
            fmt.Println(err)
        }
    }

    s.Display()
}

func (s *Sudoku) Display() {
    ClearScreen()

    rowCount := 0
    for _, row := range s {
        rowCount++
        colCount := 0
        for _, v := range row {
            colCount++
            fmt.Print(v.Val)

            switch colCount % 3 {
            case 0:
                fmt.Print("|")
            default:
                fmt.Print(" ")
            }
        }
        fmt.Println()
        if rowCount%3 == 0 {
            fmt.Println("------------------")
        }
    }

    if !s.isFinish() {
        handleSudoku(s)
    }
}

func (s *Sudoku) fill(val int8, pos Coordinate) error {
    // Done: 将一个数字放置到指定位置的方格里面, 并且在该数字不符合任一规则时返回相应的错误
    if !inputCheck(val, pos) {
        return errors.New("超出可输入范围")
    }
    if s[pos.X][pos.Y].Preset {
        return errors.New("这是个预设值，无法修改")
    }

    err := s.ruleCheck(val, pos)
    if err != nil {
        return err
    }
    s[pos.X][pos.Y].Val = val
    return nil
}

func (s Sudoku) ruleCheck(val int8, pos Coordinate) error {
    /* Done:
     * 1. 同行无重复
     * 2. 同列无重复
     * 3. 同区域无重复
    **/
    area := s[pos.X][pos.Y].Area
    for x := 0; x < len(s); x++ {
        for y := 0; y < len(s[x]); y++ {
            p := Coordinate{x, y}
            same := p.X == pos.X || p.Y == pos.Y || s[x][y].Area == area
            if p != pos && s[x][y].Val == val && same {
                if p.Y == pos.Y {
                    return errors.New("行内已有重复值，无法填入")
                } else if p.X == pos.X {
                    return errors.New("列内已有重复值，无法填入")
                } else if s[x][y].Area == area {
                    return errors.New("区域内已有重复值，无法填入")
                }
            }
        }
    }
    return nil
}

func (s *Sudoku) clear(pos Coordinate) error {
    // Done: 清除指定方格
    if !inputCheck(pos) {
        return errors.New("超出可输入范围")
    }
    if !s[pos.X][pos.Y].Preset {
        s[pos.X][pos.Y].Val = 0
    }
    return nil
}

func (s *Sudoku) isFinish() bool {
    count := 0
    for _, row := range s {
        for _, val := range row {
            if val.Val == 0 {
                count++
            }
        }
    }
    return count == 0
}
